﻿'    Copyright (C) 2009-2010  Ryan Skeldon <psykad@gmail.com>
'
'    This program is free software; you can redistribute it and/or modify
'    it under the terms of the GNU General Public License as published by
'    the Free Software Foundation; either version 2 of the License, or
'    (at your option) any later version.
'
'    This program is distributed in the hope that it will be useful,
'    but WITHOUT ANY WARRANTY; without even the implied warranty of
'    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
'    GNU General Public License for more details.
'
'    You should have received a copy of the GNU General Public License
'    along with this program; if not, write to the Free Software
'    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Option Explicit On
Imports System.Text
Imports System.Security.Cryptography
Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.ComponentModel
Imports System.Reflection
Imports Blitz_Server.Objects

Public Class Core
    Public Sub New(ByVal MainSyncObject As ISynchronizeInvoke)
        _syncObj = MainSyncObject
    End Sub

#Region "Propterties"
    ''' <summary>
    ''' Used in listening for new socket connections.
    ''' </summary>
    ''' <remarks></remarks>
    Private _listenThread As Thread

    ''' <summary>
    ''' Processes each Client's queue.
    ''' </summary>
    ''' <remarks></remarks>
    Private _processThread As Thread

    ''' <summary>
    ''' Runs through the incoming data queue.
    ''' </summary>
    ''' <remarks></remarks>
    Private _parserThread As Thread

    ''' <summary>
    ''' Used in conjuction with _stopFlag as a locking object.
    ''' </summary>
    ''' <remarks></remarks>
    Private _stopSyncObj As New Object
    Private _stopFlag As Boolean

    ''' <summary>
    ''' Contains the master list of connected clients.
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared _clients As New ArrayList
    Public Shared ReadOnly Property Clients() As ArrayList
        Get
            Return _clients
        End Get
    End Property

    ''' <summary>
    ''' Contains the master queue of incoming data.
    ''' </summary>
    ''' <remarks></remarks>
    Private _incomingData As New Queue

    ''' <summary>
    ''' Main listening socket.
    ''' </summary>
    ''' <remarks></remarks>
    Private _listener As TcpListener

    ''' <summary>
    ''' Contains the calling form object.
    ''' </summary>
    ''' <remarks></remarks>
    Private _syncObj As ISynchronizeInvoke

    Private _hostAddress As IPAddress
    ''' <summary>
    ''' Gets or sets the IP address the server runs on.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property HostAddress() As IPAddress
        Get
            Return _hostAddress
        End Get
        Set(ByVal value As IPAddress)
            _hostAddress = value
        End Set
    End Property

    Private _hostPort As Integer
    ''' <summary>
    ''' Gets or sets the port the server runs on.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property HostPort() As Integer
        Get
            Return _hostPort
        End Get
        Set(ByVal value As Integer)
            _hostPort = value
        End Set
    End Property

    Private _bufferSize As Integer
    ''' <summary>
    ''' Gets or sets the receive buffer for the server.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property BufferSize() As Integer
        Get
            Return _bufferSize
        End Get
        Set(ByVal value As Integer)
            _bufferSize = value
        End Set
    End Property

    Private _welcome As String
    ''' <summary>
    ''' Gets or sets the server's welcome message.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Welcome() As String
        Get
            Return _welcome
        End Get
        Set(ByVal value As String)
            _welcome = value
        End Set
    End Property

    ''' <summary>
    ''' Gets the count on clients connected to the server.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property ConnectedClients() As Integer
        Get
            SyncLock _clients
                Return _clients.Count
            End SyncLock
        End Get
    End Property

    Private _isRunning As Boolean
    ''' <summary>
    ''' Gets the running status of the server.
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property IsRunning() As Boolean
        Get
            Return _isRunning
        End Get
    End Property
#End Region

#Region "Events"
#Region "ConnectionsChanged"
    Public Event OnConnectionsChanged(ByVal NumConnections As Integer)
    Private Delegate Sub ConnectionsChangedCallBack(ByVal NumConnections As Integer)
    Private Sub DoConnectionsChanged(ByVal NumConnections As Integer)
        RaiseEvent OnConnectionsChanged(NumConnections)
    End Sub
    Private Sub ConnectionsChanged(ByVal NumConnections As Integer)
        Dim cb As New ConnectionsChangedCallBack(AddressOf DoConnectionsChanged)
        Dim args() As Object = {NumConnections}

        _syncObj.Invoke(cb, args)
    End Sub
#End Region

#Region "ServerMessage"
    Public Event OnServerMessage(ByVal Message As String)
    Private Delegate Sub ServerMessageCallback(ByVal Message As String)
    Private Sub DoServerMessage(ByVal Message As String)
        RaiseEvent OnServerMessage(Message)
    End Sub
    Private Sub ServerMessage(ByVal Message As String)
        Dim cb As New ServerMessageCallback(AddressOf DoServerMessage)
        Dim args() As Object = {Message}

        _syncObj.Invoke(cb, args)
    End Sub
#End Region
#End Region

#Region "Public Methods"
    ''' <summary>
    ''' Attempts to start the server's threads.
    ''' </summary>
    ''' <returns>True if threads were successfully started.</returns>
    ''' <remarks></remarks>
    Public Function StartServer() As Boolean
        If _listener IsNot Nothing Then
            StopServer()
        End If

        Try
            ServerMessage("Starting queue processor...")
            _processThread = New Thread(AddressOf _processQueues)
            _processThread.Start()

            ServerMessage("Starting parser...")
            _parserThread = New Thread(AddressOf _parser)
            _parserThread.Start()

            ServerMessage("Starting socket listener...")
            _listenThread = New Thread(AddressOf _startServer)
            _listenThread.Start()

            ServerMessage("Server is running.")
            Return True
        Catch ex As Exception
            MsgBox(ex.Message)
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Attempts to shutdown the server.
    ''' </summary>
    ''' <remarks>Clients are notified that the server is shutting down.</remarks>
    Public Sub StopServer()
        SyncLock _stopSyncObj
            _stopFlag = True
        End SyncLock

        ServerMessage("Stopping listener...")
        If _listener IsNot Nothing Then
            Try
                _listener.Stop()
            Catch ex As Exception
            End Try
        End If

        Thread.Sleep(100)

        ServerMessage("Disconnecting clients...")
        SyncLock _clients
            For Each Client As IClient In _clients
                Try
                    Client.Send("Server is shutting down, goodbye!")
                    Client.Socket.Shutdown(SocketShutdown.Both)
                    Client.Socket.Close()
                Catch
                End Try
            Next
        End SyncLock

        _clients.Clear()
        _listener = Nothing
        _listenThread = Nothing
        _stopFlag = False
        _isRunning = True

        ServerMessage("Server shutdown complete.")
        DoConnectionsChanged(0)
    End Sub

    ''' <summary>
    ''' Converts a given string into a MD5 hash.
    ''' </summary>
    ''' <param name="data">Data to be converted.</param>
    ''' <returns>The MD5 hash of the given data.</returns>
    ''' <remarks></remarks>
    Public Shared Function GenerateHash(ByVal data As String) As String
        Dim encoder As New UnicodeEncoding()
        Dim byteSourceText() As Byte = encoder.GetBytes(data)
        Dim md5 As New MD5CryptoServiceProvider()
        Dim byteHash() As Byte = md5.ComputeHash(byteSourceText)
        Return Convert.ToBase64String(byteHash)
    End Function
#End Region

#Region "Private Methods"
    ''' <summary>
    ''' The socket listening thread.
    ''' </summary>
    ''' <remarks>Starts a listening socket to accept clients.</remarks>
    Private Sub _startServer()
        Dim client As IClient
        Dim incomingSocket As Socket
        Dim welcome() As Byte = ASCIIEncoding.ASCII.GetBytes(_welcome & vbCrLf & vbCrLf)

        Try
            ConnectionsChanged(0)

            _listener = New TcpListener(HostAddress, HostPort)
            _listener.Start()
            _isRunning = True

            Do
                incomingSocket = _listener.AcceptSocket
                If Not incomingSocket Is Nothing Then
                    Dim connectionCount As Integer = -1

                    client = New IClient(incomingSocket)
                    client.SendQueue.Enqueue(welcome)
                    SyncLock _clients
                        _clients.Add(client)
                        connectionCount = _clients.Count
                    End SyncLock

                    If connectionCount > -1 Then
                        ConnectionsChanged(connectionCount)
                    End If
                End If
            Loop
        Catch ex As Exception
            Debug.WriteLine(ex.Message)
        End Try
    End Sub

    ''' <summary>
    ''' Thread used to dequeue incoming data to be parsed.
    ''' </summary>
    ''' <remarks></remarks>
    Private Sub _parser()
        Do
            SyncLock _incomingData
                If _incomingData.Count > 0 Then
                    '// Parse the data.
                End If
            End SyncLock

            Thread.Sleep(10)

            SyncLock _stopSyncObj
                If _stopFlag Then
                    Exit Do
                End If
            End SyncLock
        Loop
    End Sub

    ''' <summary>
    ''' A thread that processes incoming and outgoing client queues.
    ''' </summary>
    ''' <remarks>The thread performs the following functions:
    ''' Flag disconnected clients for removal
    ''' Sends all queued items for each client
    ''' Sends incoming data from each client to the parser
    ''' Removes disconnected clients</remarks>
    Private Sub _processQueues()
        Dim buffer(BufferSize - 1) As Byte
        Dim len As Integer

        Do
            SyncLock _clients
                For Each Client As IClient In _clients
                    If Client.IsConnected = False Then
                        Client.RemoveFlag = True
                    Else
                        If Client.SendQueue.Count > 0 Then
                            SyncLock Client.SendQueue.Peek
                                Dim bytes() As Byte = CType(Client.SendQueue.Dequeue, Byte())

                                Try
                                    Client.Socket.Send(bytes, bytes.Length, SocketFlags.None)
                                Catch ex As SocketException
                                    Client.RemoveFlag = True
                                End Try
                            End SyncLock
                        End If

                        SyncLock _stopSyncObj
                            If _stopFlag Then
                                Exit Do
                            End If
                        End SyncLock

                        If Client.RemoveFlag = False Then
                            If Client.Socket IsNot Nothing Then
                                If Client.HasData Then
                                    Try
                                        len = Client.Socket.Receive(buffer, buffer.Length, SocketFlags.None)

                                        If len > 0 Then
                                            Dim bytes(len - 1) As Byte
                                            Array.Copy(buffer, bytes, len)

                                            SyncLock _incomingData
                                                _incomingData.Enqueue(bytes)
                                            End SyncLock
                                        End If
                                    Catch ex As SocketException
                                        Client.RemoveFlag = True
                                    End Try
                                End If
                            End If
                        End If
                    End If
                Next
            End SyncLock

            Thread.Sleep(10)

            SyncLock _clients
                Dim Removed As Boolean
                Dim CurCount As Integer = _clients.Count

                Do
                    Removed = False

                    For i As Integer = 0 To _clients.Count - 1
                        If CType(_clients(i), IClient).RemoveFlag Then
                            _clients.Remove(_clients(i))
                            Removed = True
                            Exit For
                        End If
                    Next
                Loop Until Removed = False
                If CurCount <> _clients.Count Then
                    ConnectionsChanged(_clients.Count)
                End If
            End SyncLock

            SyncLock _stopSyncObj
                If _stopFlag Then
                    Exit Do
                End If
            End SyncLock
        Loop
    End Sub

    ''' <summary>
    ''' Enqueues a global message to be sent to all clients.
    ''' </summary>
    ''' <param name="Message"></param>
    ''' <remarks></remarks>
    Private Sub _broadcast(ByVal Message As Byte())
        SyncLock Message
            For Each client As IClient In _clients
                client.SendQueue.Enqueue(Message)
            Next
        End SyncLock
    End Sub
#End Region
End Class
